package com.cy.pj.sys.common;

import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;

/**
 * @Configuration 注解描述的类为springboot工程中的配置类
 *                此类的实例由spring创建和管理
 */
@Configuration
public class SpringShiroConfig {

    /**
     * 配置SecurityManager，此对象是shiro框架的核心
     * 负责认证和授权业务的实现
     * 当由spring框架整合一个第三方的bean对象时
     * 这个类型不是我们自己写的，无法在类上使用类似@Component的注解进行描述
     * 那么如何整合这样的bean呢？  --自己在spring中的配置类中定义方法
     *                             并且在方法内部构建对象实例
     *                             然后在方法上使用@Bean注解描述即可
     *                             这个注解描述的方法的返回值会交给spring管理
     *                             bean的名字默认是方法名
     * @return
     */
    //站在java多态特性应用的角度分析，方法的返回值、参数列表的类型能用抽象尽量使用抽象类型
    @Bean("securityManager")
    public SecurityManager securityManager(Realm realm,
                                           CacheManager shiroCacheManager,
                                           RememberMeManager rememberMeManager,
                                           SessionManager sessionManager
    ){
        //在web应用中，securityManager的具体实现建议使用DefaultWebSecurityManager对象
        //在非web应用中建议使用DefaultSecurityManager对象
        DefaultWebSecurityManager securityManager=
                new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        securityManager.setCacheManager(shiroCacheManager);
        securityManager.setRememberMeManager(rememberMeManager);
        securityManager.setSessionManager(sessionManager);
        return securityManager;
    }

    /**
     * 配置认证过滤规则
     *      --哪些资源需要认证访问
     *      --哪些资源可以匿名访问
     * 这个规则我们来定义,规则的检验在shiro框架中是借助大量过滤器(Filter)实现的
     * shiro框架中提供了过滤器类型，但是基于其类型创建过滤器实例需要通过过滤器工厂
     * 而这里配置的FactoryBean就是一个用于创建过滤器工厂的一个对象
     * (spring框架中所有FactoryBean的作用都是创建过滤器工厂的)
     * @return
     */
    @Bean //加了@Bean注解，会自动从spring容器中找securityManager对象
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean=
                new ShiroFilterFactoryBean();
        //这里用map存储规则，要求规则有一定的顺序，所以要使用LinkedHashMap
        //key为资源名，value为规则
        LinkedHashMap<String,String> map=new LinkedHashMap<>();
        // /bower_components/** 表示这个目录下包括子目录的所有资源，anon表示可以匿名访问
        map.put("/bower_components/**", "anon");
        map.put("/build/**", "anon");
        map.put("/dist/**", "anon");
        map.put("/plugins/**", "anon");
        map.put("/user/doLogin", "anon");//放行登录页面
        map.put("/doLogout", "logout");//当value为Logout时，退出时会自动回到登录页面
        //表示除了以上资源，后面所有的资源都要认证访问，authc表示需要认证
        //map.put("/**", "authc");
        map.put("/**", "user");//此方式的认证还可以从客户端的cookie中获取用户信息
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        //要通过securityManager对象判定这个资源是否经过认证
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //假如访问一个需要认证的资源，但这个用户还没有通过认证，需要跳转到认证页面
        shiroFilterFactoryBean.setLoginUrl("/doLoginUI");
        return shiroFilterFactoryBean;
    }

    /**
     * 这里的advisor负责找到类中使用@RequiresPermissions注解描述的方法
     * 这些方法为授权访问的切入点方法，当在执行这些方法会由通知(Advice)对象
     * 调用一个对象(SecurityManager)完成权限检测及授权
     * @param securityManager
     * @return
     */

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    /**
     * 缓存
     * @return
     */
    @Bean
    public CacheManager shiroCacheManager(){
        return new MemoryConstrainedCacheManager();
    }

    /**
     *
     * @return
     */
    @Bean
    public RememberMeManager rememberMeManager(){
        CookieRememberMeManager rememberMeManager=new CookieRememberMeManager();
        //创建cookie对象，此对象负责存储用户状态信息，并将状态信息写到客户端
        SimpleCookie cookie=new SimpleCookie("rememberMe");
        cookie.setMaxAge(7*24*60*60);//设置Cookie生命周期
        rememberMeManager.setCookie(cookie);
        return rememberMeManager;
    }

    @Bean
    public SessionManager sessionManager(){
        DefaultWebSessionManager session=new DefaultWebSessionManager();
        session.setGlobalSessionTimeout(2*1800000l);//超时时间，默认为30min
        session.setSessionIdUrlRewritingEnabled(false);//不重写URL
        return session;
    }
}
